﻿/*************************************************************************
 *
 * Copyright (c) 2009-2015 Xuld. All rights reserved.
 * 
 * Project Url: http://work.xuld.net/circus
 * 
 * This source code is part of the Project Circus.
 * 
 * This code is licensed under The Circus License.
 * See the file License.html for the license details.
 * 
 * 
 * You must not remove this notice, or any other, from this software.
 *
 * 
 *************************************************************************/


using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace Circus.Utility {
    
    /// <summary>
    /// 提供处理图片的常用工具。
    /// </summary>
    public static class ImageHelper {

        #region 图片格式

        /// <summary>
        /// 根据指定的文件扩展名返回对应的 <see cref="ImageFormat"/> 。
        /// </summary>
        /// <param name="extension">要查询的图片文件扩展名(含点、且必须小写)。</param>
        /// <returns>一个 <see cref="ImageFormat"/>，和 <paramref name="extension"/> 对应。如果查询的格式未被支持，则返回 null 。</returns>
        public static ImageFormat getImageFormat(string extension) {
            switch (extension) {
                case ".jpe":
                case ".jpeg":
                case ".jpg":
                    return ImageFormat.Jpeg;
                case ".png":
                    return ImageFormat.Png;
                case ".tif":
                case ".tiff":
                    return ImageFormat.Tiff;
                case ".bmp":
                    return ImageFormat.Bmp;
                case ".gif":
                    return ImageFormat.Gif;
                case ".emf":
                    return ImageFormat.Emf;
                case ".exif":
                    return ImageFormat.Exif;
                case ".ico":
                    return ImageFormat.Icon;
                case ".wmf":
                    return ImageFormat.Wmf;
                default:
                    return null;
            }
        }

        /// <summary>
        /// 根据指定的 <see cref="ImageFormat"/> 返回对应的文件扩展名。
        /// </summary>
        /// <param name="imageFormat">要查询的 <see cref="ImageFormat"/> 。</param>
        /// <returns>文件扩展名(含点，且小写)。如果查询的格式未被支持，则返回 null 。</returns>
        public static string getExtension(this ImageFormat imageFormat) {
            if (imageFormat == ImageFormat.Jpeg)
                return ".jpg";
            else if (imageFormat == ImageFormat.Png)
                return ".png";
            else if (imageFormat == ImageFormat.Tiff)
                return ".tif";
            else if (imageFormat == ImageFormat.Bmp)
                return ".bmp";
            else if (imageFormat == ImageFormat.Gif)
                return ".gif";
            else if (imageFormat == ImageFormat.Emf)
                return ".emf";
            else if (imageFormat == ImageFormat.Exif)
                return ".exif";
            else if (imageFormat == ImageFormat.Icon)
                return ".ico";
            else if (imageFormat == ImageFormat.Wmf)
                return ".wmf";
            else
                return null;
        }

        #endregion

        #region 画板

        /// <summary>
        /// 在指定位置并且用指定的 <see cref="Font"/> 和 <see cref="Color"/> 对象绘制指定的文本字符串。
        /// </summary>
        /// <param name="graphics">要操作的 <see cref="Graphics"/> 。</param>
        /// <param name="s">要绘制的字符串。</param>
        /// <param name="font"><see cref="Font"/>，它定义字符串的文本格式。</param>
        /// <param name="color"><see cref="Color"/>，它确定所绘制文本的颜色。</param>
        /// <param name="x">所绘制文本的左上角的 x 坐标。</param>
        /// <param name="y">所绘制文本的左上角的 y 坐标。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="s"/> 或 <paramref name="font"/> 或 <paramref name="color"/> 为 null。</exception>
        public static void drawString(this Graphics graphics, string s, Font font, Color color, float x, float y) {
            using (SolidBrush bs = new SolidBrush(color)) {
                graphics.DrawString(s, font, bs, x, y);
            }
        }

        /// <summary>
        /// 在指定位置并使用指定的大小和透明度绘制指定的 <see cref="Image"/> 的指定部分。
        /// </summary>
        /// <param name="graphics">要操作的 <see cref="Graphics"/> 。</param>
        /// <param name="image">要绘制的 <see cref="Image"/>。</param>
        /// <param name="destRect">System.Drawing.Rectangle 结构，它指定所绘制图像的位置和大小。将图像进行缩放以适合该矩形。</param>
        /// <param name="srcX">要绘制的源图像部分的左上角的 x 坐标。</param>
        /// <param name="srcY">要绘制的源图像部分的左上角的 y 坐标。</param>
        /// <param name="srcWidth">要绘制的源图像部分的宽度。</param>
        /// <param name="srcHeight">要绘制的源图像部分的高度。</param>
        /// <param name="opacity">绘制的透明度。</param>
        /// <param name="srcUnit"><see cref="GraphicsUnit"/> 枚举的成员，它指定用于确定源矩形的度量单位。</param>
        /// <param name="callBack">一个 <see cref="Image.GetThumbnailImageAbort"/> 委托。在 GDI+ 1.0 版中不使用此委托。即便如此，也必须创建一个委托并在该参数中传递对此委托的引用。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="image"/> 为 null。</exception>
        public static void drawImage(this Graphics graphics, Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, float opacity, GraphicsUnit srcUnit = GraphicsUnit.Pixel, Graphics.DrawImageAbort callBack = null) {
            using (ImageAttributes imageAttributes = getImageAttributes(opacity)) {
                graphics.DrawImage(image, destRect, srcX, srcY, srcWidth, srcHeight, srcUnit, imageAttributes, callBack);
            }
        }

        /// <summary>
        /// 在指定位置并使用指定的大小和透明度绘制指定的 <see cref="Image"/> 的指定部分。
        /// </summary>
        /// <param name="graphics">要操作的 <see cref="Graphics"/> 。</param>
        /// <param name="image">要绘制的 <see cref="Image"/>。</param>
        /// <param name="destRect">System.Drawing.Rectangle 结构，它指定所绘制图像的位置和大小。将图像进行缩放以适合该矩形。</param>
        /// <param name="srcX">要绘制的源图像部分的左上角的 x 坐标。</param>
        /// <param name="srcY">要绘制的源图像部分的左上角的 y 坐标。</param>
        /// <param name="srcWidth">要绘制的源图像部分的宽度。</param>
        /// <param name="srcHeight">要绘制的源图像部分的高度。</param>
        /// <param name="opacity">绘制的透明度。</param>
        /// <param name="srcUnit"><see cref="GraphicsUnit"/> 枚举的成员，它指定用于确定源矩形的度量单位。</param>
        /// <param name="callBack">一个 <see cref="Image.GetThumbnailImageAbort"/> 委托。在 GDI+ 1.0 版中不使用此委托。即便如此，也必须创建一个委托并在该参数中传递对此委托的引用。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="image"/> 为 null。</exception>
        public static void drawImage(this Graphics graphics, Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, float opacity, GraphicsUnit srcUnit = GraphicsUnit.Pixel, Graphics.DrawImageAbort callBack = null) {
            using (ImageAttributes imageAttributes = getImageAttributes(opacity)) {
                graphics.DrawImage(image, destRect, srcX, srcY, srcWidth, srcHeight, srcUnit, imageAttributes, callBack);
            }
        }

        /// <summary>
        /// 在指定位置并使用指定的大小和透明度绘制指定的 <see cref="Image"/> 的指定部分。
        /// </summary>
        /// <param name="graphics">要操作的 <see cref="Graphics"/> 。</param>
        /// <param name="image">要绘制的 <see cref="Image"/>。</param>
        /// <param name="destRect">System.Drawing.Rectangle 结构，它指定所绘制图像的位置和大小。将图像进行缩放以适合该矩形。</param>
        /// <param name="opacity">绘制的透明度。</param>
        /// <param name="callBack">一个 <see cref="Image.GetThumbnailImageAbort"/> 委托。在 GDI+ 1.0 版中不使用此委托。即便如此，也必须创建一个委托并在该参数中传递对此委托的引用。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="image"/> 为 null。</exception>
        public static void drawImage(this Graphics graphics, Image image, Rectangle destRect, float opacity, Graphics.DrawImageAbort callBack = null) {
            using (ImageAttributes imageAttributes = getImageAttributes(opacity)) {
                graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttributes, callBack);
            }
        }

        private static ImageAttributes getImageAttributes(float opacity) {
            ImageAttributes imageAttributes = new ImageAttributes();
            ColorMap colorMap = new ColorMap();
            colorMap.OldColor = Color.FromArgb(255, 0, 255, 0);
            colorMap.NewColor = Color.FromArgb(0, 0, 0, 0);
            ColorMap[] remapTable = { colorMap };
            imageAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);
            float[][] colorMatrixElements = {
                                                new float[] {1.0f,  0.0f,  0.0f,  0.0f, 0.0f},
                                                new float[] {0.0f,  1.0f,  0.0f,  0.0f, 0.0f},
                                                new float[] {0.0f,  0.0f,  1.0f,  0.0f, 0.0f},
                                                new float[] {0.0f,  0.0f,  0.0f,  opacity, 0.0f},
                                                new float[] {0.0f,  0.0f,  0.0f,  0.0f, 1.0f}
                                            };
            ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
            imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

            return imageAttributes;
        }

        #endregion

        #region 马赛克水印

        /// <summary>
        /// 为指定的 <see cref="Image"/> 绘制文字。
        /// </summary>
        /// <param name="image">要操作的 <see cref="Image"/>。</param>
        /// <param name="value">添加的文字。</param>
        /// <param name="font">文字的字体。</param>
        /// <param name="color">文字的颜色，允许使用透明度。</param>
        /// <param name="x">文字的 x 坐标位置。</param>
        /// <param name="y">文字的 y 坐标位置。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="image"/> 或 <paramref name="value"/> 或 <paramref name="font"/> 或 <paramref name="color"/> 为 null。</exception>
        /// <exception cref="System.Exception"><paramref name="image"/> 具有索引像素格式，或者格式未定义。</exception>
        public static void drawString(this Image image, string value, Font font, Color color, float x, float y) {
            using (Graphics graphics = Graphics.FromImage(image)) {
                drawString(graphics, value, font, color, x, y);
            }
        }

        /// <summary>
        /// 为指定的 <see cref="Image"/> 添加文字水印。
        /// </summary>
        /// <param name="image">要操作的 <see cref="Image"/>。</param>
        /// <param name="value">添加的文字。</param>
        /// <param name="font">文字的字体。</param>
        /// <param name="color">文字的颜色，允许使用透明度。</param>
        /// <param name="aligment">文字的在矩形区域的位置。</param>
        /// <param name="paddingX">源区域的 x 方向内边距。</param>
        /// <param name="paddingY">源区域的 y 方向内边距。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="image"/> 或 <paramref name="value"/> 或 <paramref name="font"/> 或 <paramref name="color"/> 为 null。</exception>
        /// <exception cref="System.Exception"><paramref name="image"/> 具有索引像素格式，或者格式未定义。</exception>
        public static void drawString(this Image image, string value, Font font, Color color, ContentAlignment aligment = ContentAlignment.BottomRight, int paddingX = 20, int paddingY = 20) {
            using (Graphics graphics = Graphics.FromImage(image)) {
                SizeF size = graphics.MeasureString(value, font);
                Point p = D2DHelper.getPosition(new Rectangle(0, 0, image.Width, image.Height), aligment, (int)size.Width, (int)size.Height, paddingX, paddingY);
                drawString(graphics, value, font, color, p.X, p.Y);
            }
        }

        /// <summary>
        /// 为指定的 <see cref="Image"/> 添加图片水印。
        /// </summary>
        /// <param name="image">要操作的 <see cref="Image"/>。</param>
        /// <param name="watermark">水印的源文件。</param>
        /// <param name="destRect">表示水印在目标的位置和大小。</param>
        /// <param name="opacity">水印的透明度。</param>
        /// <param name="callBack">一个 <see cref="Image.GetThumbnailImageAbort"/> 委托。在 GDI+ 1.0 版中不使用此委托。即便如此，也必须创建一个委托并在该参数中传递对此委托的引用。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="watermark"/> 为空。</exception>
        /// <exception cref="System.Exception"><paramref name="image"/> 具有索引像素格式，或者格式未定义。</exception>
        public static void drawWatermark(this Image image, Image watermark, Rectangle destRect, float opacity = 0.6F, Graphics.DrawImageAbort callBack = null) {
            using (Graphics graphics = Graphics.FromImage(image)) {
                drawImage(graphics, watermark, destRect, opacity, callBack);
            }
        }

        /// <summary>
        /// 为指定的 <see cref="Image"/> 添加图片水印。
        /// </summary>
        /// <param name="image">要操作的 <see cref="Image"/>。</param>
        /// <param name="watermark">水印的源文件。</param>
        /// <param name="aligment">水印文字的在矩形区域的位置。</param>
        /// <param name="paddingX">源区域的 x 方向内边距。</param>
        /// <param name="paddingY">源区域的 y 方向内边距。</param>
        /// <param name="opacity">水印的透明度。</param>
        /// <param name="callBack">一个 <see cref="Image.GetThumbnailImageAbort"/> 委托。在 GDI+ 1.0 版中不使用此委托。即便如此，也必须创建一个委托并在该参数中传递对此委托的引用。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="image"/> 或 <paramref name="watermark"/> 为空。</exception>
        /// <exception cref="System.Exception"><paramref name="image"/> 具有索引像素格式，或者格式未定义。</exception>
        public static void drawWatermark(this Image image, Image watermark, ContentAlignment aligment, int paddingX = 20, int paddingY = 20, float opacity = 0.6F, Graphics.DrawImageAbort callBack = null) {
            Point p = D2DHelper.getPosition(new Rectangle(0, 0, image.Width, image.Height), aligment, watermark.Width, watermark.Height, paddingX, paddingY);
            drawWatermark(image, watermark, new Rectangle(p, watermark.Size), opacity, callBack);
        }

        /// <summary>
        /// 为指定的 <see cref="Image"/> 添加图片水印。同时反复绘制，直到覆盖整个 <paramref name="image"/>。
        /// </summary>
        /// <param name="image">要操作的 <see cref="Image"/>。</param>
        /// <param name="watermark">水印的源文件。</param>
        /// <param name="offsetX">第一张图的 x 方向的偏移。</param>
        /// <param name="offsetY">第一张图的 y 方向的偏移。</param>
        /// <param name="opacity">水印的透明度。</param>
        /// <param name="angle">要绘制的源图像逆时针旋转的角度（以度为单位）。</param>
        /// <param name="splitX">重复绘图时，每个图片在 x 方向的间距。</param>
        /// <param name="splitY">重复绘图时，每个图片在 y 方向的间距。</param>
        /// <param name="callBack">一个 <see cref="Image.GetThumbnailImageAbort"/> 委托。在 GDI+ 1.0 版中不使用此委托。即便如此，也必须创建一个委托并在该参数中传递对此委托的引用。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="watermark"/> 为空。</exception>
        /// <exception cref="System.OutOfMemoryException">该文件没有有效的图像格式。 - 或 - GDI+ 不支持文件的像素格式。</exception>
        /// <exception cref="System.Exception"><paramref name="image"/> 具有索引像素格式，或者格式未定义。</exception>
        public static void drawWatermark(this Image image, Image watermark, int offsetX = 0, int offsetY = 0, float opacity = 0.6F, float angle = -30F, int splitX = 50, int splitY = 50, Graphics.DrawImageAbort callBack = null) {
            using (Graphics graphics = Graphics.FromImage(image)) {
                using (ImageAttributes imageAttributes = getImageAttributes(opacity)) {
                    int x = image.Width, y = image.Height;
                    bool flag = true;


                    graphics.TranslateTransform(x >> 1, y >> 1);
                    graphics.RotateTransform(angle);
                    int s = ((int)Math.Sqrt(x * x + y * y)) >> 1;
                    Rectangle rect = new Rectangle(-s, -s, watermark.Width, watermark.Height);
                    for (rect.Y += offsetY; rect.Y < s; rect.Y += rect.Height + splitY)
                        for (rect.X = (flag = !flag) ? (offsetX - s) : (offsetX + (rect.Width >> 1) - s); rect.X < s; rect.X += rect.Width + splitX)
                            graphics.DrawImage(watermark, rect, 0, 0, watermark.Width, watermark.Height, GraphicsUnit.Pixel, imageAttributes);
                }
            }
        }

        /// <summary>
        /// 将指定的图片指定的区域进行马赛克。
        /// </summary>
        /// <param name="image">要处理的 <see cref="Image"/> 。</param>
        /// <param name="rect">要处理的区域。</param>
        /// <param name="level">马赛克的等级。</param>
        public static void mosaic(this Bitmap image, Rectangle rect, int level = 8) {
            int w = image.Width;
            int h = image.Height;

            int stdR, stdG, stdB;

            stdR = 0;
            stdG = 0;
            stdB = 0;

            BitmapData srcData = image.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            unsafe {
                byte* p = (byte*)srcData.Scan0.ToPointer();

                for (int y = 0; y < h; y++) {
                    for (int x = 0; x < w; x++) {
                        if (y % level == 0) {
                            if (x % level == 0) {
                                stdR = p[2]; stdG = p[1]; stdB = p[0];
                            } else {
                                p[0] = (byte)stdB;
                                p[1] = (byte)stdG;
                                p[2] = (byte)stdR;
                            }
                        } else {
                            // 复制上一行
                            byte* pTemp = p - srcData.Stride;

                            p[0] = (byte)pTemp[0];
                            p[1] = (byte)pTemp[1];
                            p[2] = (byte)pTemp[2];
                        }

                        p += 3;

                    } // end of x

                    p += srcData.Stride - w * 3;

                } // end of y

                image.UnlockBits(srcData);
            }


        }

        #endregion

        #region 图片大小

        /// <summary>
        /// 返回 <see cref="Image"/> 的缩略图。
        /// </summary>
        /// <param name="image">要操作的 <see cref="Image"/> 。</param>
        /// <param name="thumbWidth">请求的缩略图的宽度（以像素为单位）。</param>
        /// <param name="thumbHeight">请求的缩略图的高度（以像素为单位）。</param>
        /// <param name="callBack">一个 <see cref="Image.GetThumbnailImageAbort"/> 委托。在 GDI+ 1.0 版中不使用此委托。即便如此，也必须创建一个委托并在该参数中传递对此委托的引用。</param>
        /// <param name="scales">指示是否按等比缩放的方式生成缩略图。如果值为 true， 则 <paramref name="thumbWidth"/> 和 <paramref name="thumbHeight"/> 分别表示缩略图的最大宽高，否则 <paramref name="thumbWidth"/> 和 <paramref name="thumbHeight"/> 分别表示缩略图的实际宽高。</param>
        /// <returns>表示缩略图的 <see cref="Image"/>。</returns>
        public static Image getThumbnailImage(this Image image, int thumbWidth, int thumbHeight, bool scales = false, Image.GetThumbnailImageAbort callBack = null) {
            if (scales) {
                Size actualSize = D2DHelper.scale(image.Width, image.Height, thumbWidth, thumbHeight);
                thumbWidth = actualSize.Width;
                thumbHeight = actualSize.Height;
            }

            return image.GetThumbnailImage(thumbWidth, thumbHeight, callBack, IntPtr.Zero);
        }

        /// <summary>
        /// 更改图片的大小 <paramref name="image"/> 并返回新的 <see cref="Bitmap"/> 。
        /// </summary>
        /// <param name="image">要放大的 <see cref="Image"/> 。</param>
        /// <param name="newWidth">新宽度值。</param>
        /// <param name="newHeight">新高度值。</param>
        /// <param name="interpolationMode">更改大小时使用的插入算法。</param>
        /// <param name="smoothingMode">指定是否将平滑处理（消除锯齿）应用于直线、曲线和已填充区域的边缘。</param>
        /// <param name="compositingQuality">指定在复合期间使用的质量等级。</param>
        /// <returns>包含放大后的图片信息的 <see cref="Bitmap"/> 。</returns>
        public static Image getResizedImage(this Image image, int newWidth, int newHeight, bool scales = false, InterpolationMode interpolationMode = InterpolationMode.High, SmoothingMode smoothingMode = SmoothingMode.HighQuality, CompositingQuality compositingQuality = CompositingQuality.HighQuality) {
            if (scales) {
                Size actualSize = D2DHelper.scale(image.Width, image.Height, newWidth, newHeight);
                newWidth = actualSize.Width;
                newHeight = actualSize.Height;
            }

            Bitmap resizedBmp = new Bitmap(newWidth, newHeight);
            using (Graphics graphics = Graphics.FromImage(resizedBmp)) {
                graphics.InterpolationMode = interpolationMode;
                graphics.SmoothingMode = smoothingMode;
                graphics.CompositingQuality = compositingQuality;
                graphics.DrawImage(image, new Rectangle(0, 0, newWidth, newHeight), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
            }
            return resizedBmp;
        }

        /// <summary>
        /// 更改图片的大小 <paramref name="image"/> 并返回新的 <see cref="Bitmap"/> 。
        /// </summary>
        /// <param name="image">要放大的 <see cref="Image"/> 。</param>
        /// <param name="newSize">新尺寸值。</param>
        /// <param name="interpolationMode">更改大小时使用的插入算法。</param>
        /// <param name="smoothingMode">指定是否将平滑处理（消除锯齿）应用于直线、曲线和已填充区域的边缘。</param>
        /// <param name="compositingQuality">指定在复合期间使用的质量等级。</param>
        /// <returns>包含放大后的图片信息的 <see cref="Bitmap"/> 。</returns>
        public static Image getResizedImage(this Image image, Size newSize, bool scales = false, InterpolationMode interpolationMode = InterpolationMode.High, SmoothingMode smoothingMode = SmoothingMode.HighQuality, CompositingQuality compositingQuality = CompositingQuality.HighQuality) {
            return getResizedImage(image, newSize.Width, newSize.Height, scales, interpolationMode, smoothingMode, compositingQuality);
        }

        #endregion

    }

}
